package com.idi.remote.assistant;

import com.idi.remote.assistant.RemoteAccessProvider;
import com.jcraft.jsch.*;
import org.apache.maven.plugin.MojoExecutionException;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Wrapper around ssh exec client
 *
 * @author Yossi Shaul
 */
public class SshExecClient {

	private String user;
    private String password;
    private String host;
    private int timeout;    // no timeout (equals 0)

    public SshExecClient(String user, String password, String host) {
        this.user = user;
        this.password = password;
        this.host = host;
    }
    public SshExecClient(final String host, final RemoteAccessProvider buildProperties, final int timeout) {
        this(host,buildProperties);
        setTimeout(timeout);
    }

	public SshExecClient(final String host, final RemoteAccessProvider buildProperties) {
        this(buildProperties.getDeployUser(), buildProperties.getDeployUserPassword(),host);
    }

    public int execute(String command) throws MojoExecutionException {
        try {
//            log.info("[sshexec] " + command);
            JSch jSch = new JSch();
            Session session = jSch.getSession(user, host);
            UserInfo userInfo = new MyUserInfo(password);
            session.setUserInfo(userInfo);
            session.setTimeout(timeout);
            session.connect();
            if (!session.isConnected()) {
                throw new MojoExecutionException(String.format("could not connect to %s. please verify user and password",host));
            }
            final ChannelExec exec = (ChannelExec) session.openChannel("exec");
            exec.setCommand(command);
            exec.setOutputStream(getOutputStreamDelegator(), true);
            exec.setErrStream(getOutputStreamDelegator());
            exec.connect();

            // wait for it to finish
            Thread thread = new Thread() {
                @Override
				public void run() {
                    while (!exec.isEOF()) {
                        try {
                            sleep(500);
                        } catch (Exception e) {
                            // ignored
                        }
                    }
                }
            };

            thread.start();
            thread.join(0);

            int exitStatus = exec.getExitStatus();
            exec.disconnect();
            session.disconnect();
            return exitStatus;
        } catch (JSchException e) {
            throw new MojoExecutionException("SSH failed", e);
        } catch (InterruptedException e) {
            throw new MojoExecutionException("Interrupted while waiting for ssh command to finish", e);
        }
    }

    private static DontCloseMeSystemOutDelegator dontCloseMeSystemOutDelegator = new DontCloseMeSystemOutDelegator();
    protected OutputStream getOutputStreamDelegator() {
        return dontCloseMeSystemOutDelegator;
    }

    public int getTimeout() {
        return timeout;
    }

    /**
     * Sets the command timout
     *
     * @param timeout Timout in milliseconds
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    private static class MyUserInfo implements UserInfo {
        private final String password;
        MyUserInfo(String password) {this.password = password;}
        public String getPassword() {return password;}
        public String getPassphrase() {return null;}
        public boolean promptPassword(String message) {return true;}
        public boolean promptPassphrase(String message) {return false;}
        public boolean promptYesNo(String message) {return true;}// trust all servers
        public void showMessage(String message) {}
    }

    /**
     * The jsch has a bug which will close the output stream (in our case System.out)
     * This simple class will prevent it.
     */
    private static class DontCloseMeSystemOutDelegator extends OutputStream {
        private OutputStream systemOut = System.out;
        public void write(int b) throws IOException {systemOut.write(b);}
        public void write(byte[] b) throws IOException {systemOut.write(b);}
        public void write(byte[] b, int off, int len) throws IOException {systemOut.write(b, off, len);}
        public void flush() throws IOException {systemOut.flush();}
        public void close() throws IOException {}// you won't close me!
    }
}
